import {Coverage, TestParser} from '../../test-parser'
import * as step from '@flow-step/step-toolkit'
import {checkCoverageFileExist, readFile, writeToFile} from '../../utils'

interface TestEvent {
    Action: string;
    Test?: string;
    Package?: string;
    Elapsed?: number;
}

interface PackageStats {
    Total: number;
    Passed: number;
    Failed: number;
    Skipped: number;
    Elapsed: number;
}

interface TestStats {
    TestName: string;
    Elapsed: number;
    Outcome: string;
}

export class GolangTestParser implements TestParser {
    async coverageFile(path: string): Promise<string> {
        if (!path.endsWith('.jsonl')) {
            throw new Error(`Input variable 'reportPath' should reference a report with jsonl extension`)
        }

        await checkCoverageFileExist(path)
        return path
    }

    htmlFile(path: string): string {
        return path.replace('.jsonl', '.html')
    }

    async parse(path: string): Promise<Coverage> {
        step.info(`Parsing Golang-Test json report: ${path}`)
        const fileContent = await readFile(path)
        step.debug('Parsing jsonFile content: ' + fileContent)
        return this.count(path, fileContent)
    }

    generateHtmlReport(
        total: number,
        passed: number,
        failed: number,
        skipped: number,
        packageStats: Record<string, PackageStats>,
        testStats: Record<string, TestStats>
    ): string {
        const packageRows = Object.keys(packageStats).sort().map(pkg => {
            const stats = packageStats[pkg]
            return `<tr>
                    <td>${pkg}</td>
                    <td>${stats.Total}</td>
                    <td>${stats.Passed}</td>
                    <td>${stats.Failed}</td>
                    <td>${stats.Skipped}</td>
                    <td>${stats.Elapsed.toFixed(2)}</td>
                </tr>`
        }).join('')

        const testRows = Object.keys(testStats).sort().map(testName => {
            const stats = testStats[testName]
            const outcomeClass = stats.Outcome === 'pass' ? 'pass' : (stats.Outcome === 'fail' ? 'fail' : 'skip')
            return `<tr class="${outcomeClass}">
                    <td>${testName}</td>
                    <td>${stats.Outcome}</td>
                    <td>${stats.Elapsed.toFixed(2)}</td>
                </tr>`
        }).join('')

        return `
        <!DOCTYPE html>
        <html>
        <head>
            <style>
                table, th, td {
                    border: 1px solid black;
                    border-collapse: collapse;
                }
                th, td {
                    padding: 8px;
                    text-align: left;
                }
                .pass { color: green; }
                .fail { color: red; }
                .skip { color: orange; }
            </style>
        </head>
        <body>
            <h2>Test Report</h2>
            <h3>Summary</h3>
            <table style="width:20%">
                <tr>
                    <th>Total</th>
                    <th class="pass">Passed</th>
                    <th class="fail">Failed</th>
                    <th class="skip">Skipped</th>
                </tr>
                <tr>
                    <td>${total}</td>
                    <td>${passed}</td>
                    <td>${failed}</td>
                    <td>${skipped}</td>
                </tr>
            </table>
            
            <h3>Packages</h3>
            <table style="width:80%">
                <tr>
                    <th>Package</th>
                    <th>Total</th>
                    <th>Passed</th>
                    <th>Failed</th>
                    <th>Skipped</th>
                    <th>Time(s)</th>
                </tr>
                ${packageRows}
            </table>

            <h3>Tests</h3>
            <table style="width:80%">
                <tr>
                    <th>Test</th>
                    <th>Outcome</th>
                    <th>Time(s)</th>
                </tr>
                ${testRows}
            </table>
        </body>
        </html>`
    }

    async count(path: string, jsonFile: string): Promise<Coverage> {
        try {
            let total = 0, passed = 0, failed = 0, skipped = 0
            const runningTests: Record<string, boolean> = {}
            const packageStats: Record<string, PackageStats> = {}
            const testStats: Record<string, TestStats> = {}

            const lines = jsonFile.split('\n').filter(line => line.length !== 0)
            for await (const line of lines) {
                let event: TestEvent
                try {
                    event = JSON.parse(line)
                } catch (err) {
                    step.error(`'Error parsing line ${line} to json:' ${err}`)
                    continue
                }

                let packageName = event.Package || ''
                if (!packageName && event.Test) {
                    const parts = event.Test.split('/')
                    if (parts.length > 0) {
                        packageName = parts[0]
                    }
                }

                const fullTestName = `${packageName}/${event.Test || ''}`
                if (!packageStats[packageName]) {
                    packageStats[packageName] = {Total: 0, Passed: 0, Failed: 0, Skipped: 0, Elapsed: 0}
                }

                switch (event.Action) {
                    case 'run':
                        if (!runningTests[fullTestName]) {
                            total++
                            packageStats[packageName].Total++
                            runningTests[fullTestName] = true
                        }
                        break
                    case 'pass':
                        if (runningTests[fullTestName]) {
                            passed++
                            packageStats[packageName].Passed++
                            packageStats[packageName].Elapsed += event.Elapsed || 0
                            testStats[fullTestName] = {
                                TestName: fullTestName,
                                Elapsed: event.Elapsed || 0,
                                Outcome: 'pass'
                            }
                            delete runningTests[fullTestName]
                        }
                        break
                    case 'fail':
                        if (runningTests[fullTestName]) {
                            failed++
                            packageStats[packageName].Failed++
                            testStats[fullTestName] = {
                                TestName: fullTestName,
                                Elapsed: event.Elapsed || 0,
                                Outcome: 'fail'
                            }
                            delete runningTests[fullTestName]
                        }
                        break
                    case 'skip':
                        if (runningTests[fullTestName]) {
                            skipped++
                            packageStats[packageName].Skipped++
                            testStats[fullTestName] = {
                                TestName: fullTestName,
                                Elapsed: event.Elapsed || 0,
                                Outcome: 'skip'
                            }
                            delete runningTests[fullTestName]
                        }
                        break
                }
            }

            const htmlContent = this.generateHtmlReport(total, passed, failed, skipped, packageStats, testStats)
            await writeToFile(this.htmlFile(path), htmlContent)

            return {
                total: total,
                passed: passed,
                failed: failed,
                skipped: skipped,
                passedRate: Math.round(passed / total * 10000) / 100
            } as Coverage
        } catch (err) {
            throw new Error(`Error handling file: ${err}`)
        }
    }
}

